﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.ServiceModel;
using System.ServiceModel.Channels;
using Interfaces;

namespace Mesne
{
    public class Responder : IResponder
    {

        private ConcurrentDictionary <string, IClientCallback> callbacks;
        private ConcurrentQueue<IRoutedMessage> messages;
        private System.Timers.Timer messageTimer;

        public Responder()
        {
            callbacks = new ConcurrentDictionary<string, IClientCallback>();
            messages = new ConcurrentQueue<IRoutedMessage>();
            messageTimer = new System.Timers.Timer(100);
            messageTimer.Elapsed += messageTimer_Elapsed;
            messageTimer.Start();
        }

        void messageTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            IRoutedMessage routedMessage;
            if (messages.TryDequeue(out routedMessage))
            {
                Respond(routedMessage);
            }
        }

        public void RegisterCallback(string sessionId, IClientCallback callback)
        
        {
            callbacks[OperationContext.Current.SessionId] = callback;
            (callback as IChannel).Faulted += Responder_Faulted;
            (callback as IChannel).Closing += Responder_Closing;
        }

        void Responder_Closing(object sender, EventArgs e)
        {
            CleanUpCallback(sender as IClientCallback);
        }

        void Responder_Faulted(object sender, EventArgs e)
        {
            CleanUpCallback(sender as IClientCallback);
        }

        private void CleanUpCallback(IClientCallback clientCallback)
        {
            List<string> sessionIds = callbacks.Where(x => x.Value == clientCallback).Select(k => k.Key).ToList();
            foreach (string sessionId in sessionIds)
            {
                IClientCallback ccb;
                callbacks.TryRemove(sessionId, out ccb);
            }
        }

        public void DeregisterCallback(string sessionId)
        {
            IChannel cbChannel = callbacks[sessionId] as IChannel;
            if(cbChannel != null)
            {
                cbChannel.Faulted -= Responder_Faulted;
                cbChannel.Closing -= Responder_Closing;
                cbChannel.Close();
            }
            callbacks[sessionId] = null;
        }

        public void Add(IRoutedMessage message)
        {
            if (messages != null)
            {
                messages.Enqueue(message);
            }
        }

        private void Respond(IRoutedMessage routedMessage)
        {
            Respond(routedMessage.Message, routedMessage.SessionId);
        }

        private void Respond(string message, string sessionId = null)
        {
            if (sessionId != null)
            {
                IClientCallback callback = callbacks[sessionId];
                if (callback != null && (callback as IChannel).State == CommunicationState.Opened)
                {
                    callback.Response(message);
                }
            }
            else
            {
                foreach (IClientCallback callback in callbacks.Values)
                {
                    if (callback != null && (callback as IChannel).State == CommunicationState.Opened)
                    {
                        callback.Response(message);
                    }
                }
            }
        }
    }
}
